package com.example.jdbc.framework;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.StringUtils;

import javax.persistence.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * @Classname EntityOperation
 * @Date 2019/5/16 23:12
 * @Author admin
 * @Description TODO
 */
public class EntityOperation<T> {

    private Logger logger = LoggerFactory.getLogger(EntityOperation.class);

    public Class<T> entityClass;

    public final Map<String, PropertyMapping> mappings;

    public final RowMapper<T> rowMapper;

    public final String tableName;

    public String allColumn = "*";

    public Field pkField;

    public EntityOperation(Class<T> entityClass, String pk) throws Exception {
        if (!entityClass.isAnnotationPresent(Entity.class)) {
            throw new Exception("在" + entityClass.getName() + "中没有找到Entity注解，不能做ORM映射");
        }

        this.entityClass = entityClass;

        Table table = entityClass.getAnnotation(Table.class);

        if (null != table) {
            this.tableName = table.name();
        } else {
            this.tableName = entityClass.getSimpleName();
        }

        Map<String, Method> getters = ClassMappings.findPublicGetters(entityClass);
        Map<String, Method> setters = ClassMappings.findPublicSetters(entityClass);

        Field[] fields = ClassMappings.findFields(entityClass);

        fillPkFieldAndAllColumn(pk, fields);

        this.mappings = getPropertyMappings(getters, setters, fields);

        this.allColumn = this.mappings.keySet().toString().replace("[", "").replace("]", "").replace(" ", "");

        this.rowMapper = createRowMapper();
    }

    private Map<String, PropertyMapping> getPropertyMappings(Map<String, Method> getters, Map<String, Method> setters, Field[] fields) {

        Map<String, PropertyMapping> mappings = new HashMap<>();

        String name;

        for (Field field : fields) {
            if (field.isAnnotationPresent(Transient.class)) continue;
            ;

            name = field.getName();

            if (name.startsWith("is")) name = name.substring(2);

            name = Character.toLowerCase(name.charAt(0)) + name.substring(1);

            Method getter = getters.get(name);
            Method setter = setters.get(name);

            if (null == setter || null == getter) continue;

            Column column = field.getAnnotation(Column.class);

            if (null == column) {
                mappings.put(field.getName(), new PropertyMapping(getter, setter, field));
            } else {
                mappings.put(column.name(), new PropertyMapping(getter, setter, field));
            }
        }

        return mappings;

    }

    RowMapper<T> createRowMapper() {

        return new RowMapper<T>() {
            @Override
            public T mapRow(ResultSet resultSet, int rowNum) throws SQLException {
                try {
                    T t = entityClass.newInstance();
                    ResultSetMetaData metaData = resultSet.getMetaData();

                    int columns = metaData.getColumnCount();

                    String columnName;
                    for (int i = 0; i < columns; i++) {
                        Object value = resultSet.getObject(i);
                        columnName = metaData.getColumnName(i);
                        fillBeanFieldValue(t, columnName, value);
                    }

                    return t;

                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }


        };

    }

    private void fillBeanFieldValue(T t, String columnName, Object value) {
        if (null != value) {
            PropertyMapping pm = mappings.get(columnName);
            if (null != pm) {
                try {
                    pm.set(t, value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void fillPkFieldAndAllColumn(String pk, Field[] fields) {
        try {
            if (!StringUtils.isEmpty(pkField)) {
                pkField = entityClass.getDeclaredField(pk);
                pkField.setAccessible(true);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            if (StringUtils.isEmpty(pk)) {
                Id id = field.getAnnotation(Id.class);
                if (null != id) {
                    pkField = field;
                    break;
                }
            }
        }
    }

    public T parse(ResultSet rs) {
        T t = null;
        if (null == rs) {
            return null;
        }

        Object value = null;

        try{
            t = entityClass.newInstance();
            for (String columnName: mappings.keySet()) {
                try{
                    value = rs.getObject(columnName);
                }catch (Exception e){
                    e.printStackTrace();
                }
                fillBeanFieldValue(t,columnName,value);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return t;
    }

    public Map<String,Object> parse(T t){
        Map<String,Object> map = new TreeMap<>();

        try{
            for (String columnName:mappings.keySet()){
                Object value = mappings.get(columnName).getter.invoke(t);
                if (value == null){
                    continue;
                }
                map.put(columnName,value);
            }
        } catch (Exception e){
            e.printStackTrace();
        }
        return map;
    }

    public void println(T t) {
        try {
            for (String columnName : mappings.keySet()) {
                Object value = mappings.get(columnName).getter.invoke(t);
                if (value == null)
                    continue;
                System.out.println(columnName + " = " + value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
